/*
  A trivial static http webserver using Libevent's evhttp.

  This is not the best code in the world, and it does some fairly stupid stuff
  that you would never want to do in a production webserver. Caveat hackor!

 */

/* system header */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>

#ifdef WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#include <io.h>
#include <fcntl.h>
#ifndef S_ISDIR
#define S_ISDIR(x) (((x) & S_IFMT) == S_IFDIR)
#endif
#else
#include <sys/stat.h>
#include <sys/socket.h>
#include <signal.h>
#include <fcntl.h>
#include <unistd.h>
#include <dirent.h>
#include <netinet/in.h>
#endif

#ifdef WIN32
#define stat _stat
#define fstat _fstat
#define open _open
#define close _close
#define O_RDONLY _O_RDONLY
#endif

/* 3rd project header */
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <event2/event.h>
#include <event2/http.h>
#include <event2/http_struct.h>
#include <event2/http_compat.h>
#include <event2/buffer.h>
#include <event2/bufferevent.h>
#include <event2/bufferevent_ssl.h>
#include <event2/util.h>
#include <event2/keyvalq_struct.h>
#include "sys/queue.h"

#include "zlib.h"

/* my project header */
#include "locketdef.h"
#include "locketerrno.h"
#include "locketlog.h"
#include "locketcommon.h"

#include "lockethttpagent.h"

static int bInit = 0;
char uri_root[512];
struct event_base *base;
static char* gpcServerHost = NULL;
static int giServerHost    = 0;
static int giProxyMode     = 0;
#define SERVER_HOST             gpcServerHost
#define SERVER_PORT             giServerHost
#define SERVER_PROXY_MODE       giProxyMode
#define PROXY_TIMEOUT_IN_SECS   60
#define HTTP_BODY_MAX_SIZE      1460 //(假设MTU=1500时, TCP部分所能携带的最大内容,1500-20字节IP头-20字节TCP头)    
#define HTTP_BODY_MIN_SIZE      100  

typedef struct tagZStreamInfo
{
    z_stream d_stream;
    int bDecompressing;
}ZSTREAM_INFO_S;
typedef struct tagHttpConnect
{
    struct evhttp_request *client_request;
    struct evhttp_request *server_request;
    int bSendData;
    ZSTREAM_INFO_S stZstream;
    SSL *ssl;
    struct bufferevent *bev;
}connect_info_s;

/* Decompress gzip or deflat data */
int gzdecompress(ZSTREAM_INFO_S *pstZstream, Byte *byInputData, size_t nInputdataLen, Byte **byOutputData, size_t *nOutputdataLen)
{
    int iErr = ERR_COMMON_SUCCESS;
    z_stream *d_stream = &pstZstream->d_stream;

    /* init zstream */
    if (0 == pstZstream->bDecompressing)
    {
        memset (d_stream, 0, sizeof (z_stream));

        /* MAX_WBITS + 32, 能够同时侦测zlib和gzip的编码 */
        iErr = inflateInit2(d_stream, MAX_WBITS+32);
        if(Z_OK != iErr) 
        {
            LOCKET_LOGERROR ("inflateInit2 failed, err:%d", iErr);
            return ERR_ZLIB_FAILED;
        }

        pstZstream->bDecompressing = 1;
    }

    int nDecompressChunk = 3 * nInputdataLen;   //默认设置output buffer为input buffer的3倍大小
    int iDecomressNum    = 0;
    d_stream->avail_in   = nInputdataLen;
    d_stream->next_in    = byInputData;
    Bytef *pTmpBuf       = NULL;
    do
    {
        pTmpBuf = d_stream->next_out = (Bytef*) realloc (pTmpBuf, nDecompressChunk * (iDecomressNum + 1));

        if (NULL == d_stream->next_out)
        {
            iErr = ERR_COMMON_NO_MEM;
            LOCKET_LOGERROR ("realloc failed");
            break;
        }
        d_stream->avail_out = nDecompressChunk;
        d_stream->next_out += nDecompressChunk * iDecomressNum;

        iErr = inflate (d_stream, Z_NO_FLUSH);
        if (Z_STREAM_END == iErr) 
        {
            /* stream data is fully decompress */
            iErr = ERR_ZLIB_STREAM_FINISH;
            break;
        }
        else if (Z_OK != iErr)
        {
            LOCKET_LOGERROR ("inflate failed, err:%d", iErr);
            iErr = ERR_ZLIB_FAILED;
            break;
        }
        else if (Z_OK == iErr)
        {
            iErr = ERR_COMMON_SUCCESS;
            break;
        }

        iDecomressNum ++;
    } while (d_stream->avail_out == 0); 

    if ((ERR_ZLIB_STREAM_FINISH == iErr)  && (1 == pstZstream->bDecompressing))
    {
        pstZstream->bDecompressing = 0;
        if(Z_OK != inflateEnd(d_stream))
        {
            LOCKET_LOGERROR ("decompress finish , but inflateEnd failed, err:%d", iErr);
            return ERR_ZLIB_FAILED;
        }
        iErr = ERR_COMMON_SUCCESS;
    }
    else if (ERR_COMMON_SUCCESS != iErr)
    {
        free (pTmpBuf);
        pstZstream->bDecompressing = 0;
        if(Z_OK != inflateEnd(d_stream))
        {
            LOCKET_LOGERROR ("inflateEnd error, err:%d", iErr);
        }
        return ERR_ZLIB_FAILED;
    }
    *nOutputdataLen += (nDecompressChunk * (iDecomressNum + 1)) - d_stream->avail_out;
    *byOutputData   = (Byte*) pTmpBuf;

    return ERR_COMMON_SUCCESS;
}

/* Callback used for the /dump URI, and for every non-GET request:
 * dumps all information to stdout and gives back a trivial 200 ok */
static void dump_request_cb(struct evhttp_request *req, void *arg)
{
	const char *cmdtype;
	struct evkeyvalq *headers;
	struct evkeyval *header;
	struct evbuffer *szCompressData;

	switch (evhttp_request_get_command(req)) {
	case EVHTTP_REQ_GET: cmdtype = "GET"; break;
	case EVHTTP_REQ_POST: cmdtype = "POST"; break;
	case EVHTTP_REQ_HEAD: cmdtype = "HEAD"; break;
	case EVHTTP_REQ_PUT: cmdtype = "PUT"; break;
	case EVHTTP_REQ_DELETE: cmdtype = "DELETE"; break;
	case EVHTTP_REQ_OPTIONS: cmdtype = "OPTIONS"; break;
	case EVHTTP_REQ_TRACE: cmdtype = "TRACE"; break;
	case EVHTTP_REQ_CONNECT: cmdtype = "CONNECT"; break;
	case EVHTTP_REQ_PATCH: cmdtype = "PATCH"; break;
	default: cmdtype = "unknown"; break;
	}

	printf("Received a %s request for %s\nHeaders:\n",
	    cmdtype, evhttp_request_get_uri(req));

    printf("  Proxy Host:%s\n", SERVER_HOST);
    printf("  Proxy Port:%d\n", SERVER_PORT);
	headers = evhttp_request_get_input_headers(req);
	for (header = headers->tqh_first; header;
	    header = header->next.tqe_next) {
		printf("  %s: %s\n", header->key, header->value);
	}

	szCompressData = evhttp_request_get_input_buffer(req);
	puts("Input data: <<<");
	while (evbuffer_get_length(szCompressData)) {
		int n;
		char cbuf[128];
		n = evbuffer_remove(szCompressData, cbuf, sizeof(cbuf));
		if (n > 0)
			(void) fwrite(cbuf, 1, n, stdout);
	}
	puts(">>>");

	evhttp_send_reply(req, 200, "OK", NULL);
} 

// called per chunk received
// chunk回调期间， ntoread = -1; chunk回调结束后，ntoread = 0;
// 如果有chunk数据，chunked = 1
// reqcb结束后,userdone才设置成1, 其他情况均为0
void chunkcb(struct evhttp_request *server_response, void *arg) 
{
    connect_info_s *connect_info = (connect_info_s*)arg;
    struct evhttp_request *client_response = connect_info->client_request;

	if(NULL == server_response)
    {
		fprintf(stdout, "http close");
        /* evhttp_send_reply之后会把数据自动清空, 若第四参数不为NULL,则需要调用者手动释放*/
		evhttp_send_reply(client_response, 504, "Bad Gateway.", NULL);
		return;
	}

    if (0 == connect_info->bSendData)
    {
       connect_info->bSendData = 1;

       struct evkeyval *header;
       TAILQ_FOREACH(header, server_response->input_headers, next) 
       {
           //evhttp_send_reply_start内部会自动添加如下字段，所以此处不需要添加
           if (0 == strncmp (header->key, "Transfer-Encoding", strlen("Transfer-Encoding")))
           {
               continue;
           }

           (void)evhttp_add_header(client_response->output_headers, (const char*)header->key, (const char*)header->value);
       }

       /* forward http to client */
       int iResponseCode = evhttp_request_get_response_code (server_response);
       const char *pcCodeLine = evhttp_request_get_response_code_line (server_response);
       evhttp_send_reply_start (client_response, iResponseCode, pcCodeLine);
    }

#if 1
    /* gzip / deflat解压 */
    const char *pcValue = evhttp_find_header(server_response->input_headers, "Content-Encoding");
    if (NULL != pcValue)
    {
        /* calc input buffer len */
        size_t response_buffer_len = evbuffer_get_length (server_response->input_buffer);
        unsigned char szCompressData[response_buffer_len];
        memset (szCompressData, 0, response_buffer_len);
        (void) evbuffer_copyout (server_response->input_buffer,  szCompressData, response_buffer_len);

        /* decompress buffer */
        if (0 == strncmp (pcValue, "gzip", strlen("gzip")))
        {
            size_t decompressDataLen  = 0;
            Byte *pucDecompressData = NULL;

            int ret = gzdecompress (&connect_info->stZstream, szCompressData, response_buffer_len, &pucDecompressData, &decompressDataLen);
            if (ERR_COMMON_SUCCESS != ret)
            {
                printf ("gzdecompress error, ret:%d\n", ret);
            }
            else
            {
                printf("%s", (char*) pucDecompressData);
                printf("-----------------------------------------------------------\n");
                free (pucDecompressData);
            }
        }
    }
    
#endif

    /* forward http to client */
    evhttp_send_reply_chunk (client_response, server_response->input_buffer);

    return;
}

// gets called when request completes
void reqcb(struct evhttp_request * server_response, void * arg) 
{
    connect_info_s *connect_info = (connect_info_s*)arg;
    struct evhttp_request *client_response = connect_info->client_request;

	if(NULL == server_response)
    {
		fprintf(stdout, "http close.");
        /* evhttp_send_reply之后会把数据自动清空, 若第四参数不为NULL,则需要调用者手动释放*/
		evhttp_send_reply(client_response, 504, "Bad Gateway.", NULL);
		return;
	}

    /* 如果没有得到response, libevent会把request返回，以便用户做些错误处理 */
    if (EVHTTP_REQUEST == server_response->kind)
    {
       fprintf (stdout, "http request failed\n");
    }

    if (connect_info->bSendData == 1)
    {
        evhttp_send_reply_end (client_response);
        connect_info->bSendData = 0;
        return;
    }

    return;
}

static SSL* load_ssl ()
{
    int iErr = ERR_COMMON_SUCCESS;

    /* An OpenSSL context holds data that new SSL connections will
     * be created from. */
    SSL_CTX *sctx = SSL_CTX_new (SSLv23_client_method ());
    if (NULL == sctx)
    {
        iErr = ERR_COMMON_OPENSSL_FAILED;
        LOCKET_LOGERROR ("SSL_CTX_new failed. err:%lu %s", iErr, LOCKET_ERR_GetString());
        return NULL;
    }

    /* Find the certificate authority (which we will use to
     * validate the server) and add it to the context. */
    iErr = SSL_CTX_load_verify_locations (sctx, "cacert.pem", NULL);
    if (ERR_COMMON_SUCCESS != iErr)
    {
        iErr = ERR_COMMON_OPENSSL_FAILED;
        LOCKET_LOGERROR ("SSL_CTX_load_verify_locations failed. err:%lu %s", iErr, LOCKET_ERR_GetString());
        return NULL;
    }

    /* need validate certificate */
    SSL_CTX_set_verify (sctx, SSL_VERIFY_PEER, NULL);

    /*  待研究这行代码 */
    //SSL_CTX_set_cert_verify_callback (sctx, cert_verify_callback, (void *) host);

    SSL *ssl = SSL_new (sctx);
    if (NULL == ssl)
    {
        iErr = ERR_COMMON_OPENSSL_FAILED;
        LOCKET_LOGERROR ("SSL_new failed. err:%lu %s", iErr, LOCKET_ERR_GetString());
        return NULL;
    }

    return ssl;
}

struct evhttp_request *proxy_request(const char *domain,
                                    unsigned short port,
                                    int timeout_in_secs,
                                    struct evhttp_request *client_request, 
                                    const char *uri)
{
    int iErr = 0;
    SSL *ssl = NULL;

    struct evhttp_connection *server_connection = NULL;
    struct evhttp_request *server_request = NULL;
    connect_info_s *connect_info = (connect_info_s*) malloc (sizeof (connect_info_s));
    if (NULL == connect_info)
    {
        return NULL;
    }
    memset (connect_info, 0, sizeof (connect_info_s));

    if (HTTPS_PROXY == SERVER_PROXY_MODE)
    {
        ssl = load_ssl ();
        if (NULL == ssl)
        {
            LOCKET_LOGERROR ("load SSL failed. err:%lu", iErr);
            return NULL;
        }
        connect_info->ssl = ssl;

        struct bufferevent *bev = NULL;
        bev = bufferevent_openssl_socket_new (base, -1, ssl, 
                                              BUFFEREVENT_SSL_CONNECTING,
                                              0 | BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
        connect_info->bev = bev;

        server_connection = evhttp_connection_base_bufferevent_new (base, 0, bev, domain, port);
    }
    else
    {
        server_connection = evhttp_connection_base_new(base, NULL, domain, port);
    }

    evhttp_connection_set_timeout(server_connection, timeout_in_secs);//如果放在evhttp_make_request会挂掉
    server_request = evhttp_request_new(reqcb, (void*)connect_info);
    evhttp_request_set_chunked_cb (server_request, chunkcb);

    connect_info->client_request = client_request;

    struct evkeyval *header;
#if 1
    TAILQ_FOREACH(header, client_request->input_headers, next) 
    {
        if (0 == strncmp (header->key, "Host", strlen("Host")))
        {
            char szHost[128] = {0};
            evutil_snprintf(szHost, sizeof(szHost), "%s:%d",domain,port);

            (void) evhttp_add_header(server_request->output_headers, (const char *)header->key, szHost);
        }
        else
        {
            (void) evhttp_add_header(server_request->output_headers, (const char *)header->key, header->value);
        }
    }
#endif
    if (client_request->type == EVHTTP_REQ_POST)
    {
        (void) evbuffer_add_buffer(server_request->output_buffer , client_request->input_buffer);
    }

    int ret = evhttp_make_request(server_connection, server_request, client_request->type, uri);
    if (0 != ret)
    {
        printf ("ret:%d\n", ret);
    }
    return server_request;
}

/* This callback gets invoked when we get any http request that doesn't match
 * any other callback.  Like any evhttp server callback, it has a simple job:
 * it must eventually call evhttp_send_iError() or evhttp_send_reply().
 */
static void send_document_cb(struct evhttp_request *req, void *arg)
{
    if (NULL == req)
    {
        return;
    }

    /* Parse uri && path */
    const char *uri             = NULL;
    int bUriOK                  = 0; 
    const struct evhttp_uri *evuri = NULL;
    do 
    {
        /* Get uri [scheme:][//host:port][path][?query][#fragment] */
        /* uri是[path](包含)后面这部分 */
        uri = evhttp_request_get_uri (req);
        if (NULL == uri)
        {
            break;
        }

        bUriOK = 1;
    } while (0);

    if (0 == bUriOK)
    {
        printf("It's not a good URI. Sending BADREQUEST\n");
        evhttp_send_error(req, HTTP_BADREQUEST, 0);
        return;
    }

    /* Get request type */
    int req_type = evhttp_request_get_command(req);
	if (req_type == EVHTTP_REQ_GET) 
    {
        printf("Got a GET request for <%s>\n",  uri);
	}
    else if (req_type == EVHTTP_REQ_POST)  
    {
        printf("Got a POST request for <%s>\n",  uri);
    }
    printf ("uri:%s\n", uri);

    /* connect proxy server */
    if (NULL == proxy_request(SERVER_HOST, SERVER_PORT, PROXY_TIMEOUT_IN_SECS, req, uri))
    {
        fprintf(stderr, "connect to server fail.");
        evhttp_send_error(req, 502, "Bad Gateway.");
        return;
    }

    return;
}

#if 0
static void syntax(void)
{
	fprintf(stdout, "Syntax: http-server <docroot>\n");
}
#endif

int LOCKET_HTTPAGENT_Init(char* host, int host_port, int proxy_mode)
{
	struct evhttp *http;
	struct evhttp_bound_socket *handle;
	unsigned short port = 8008;
#ifdef WIN32
	WSADATA WSAData;
	WSAStartup(0x101, &WSAData);
#else
	if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
		return (1);
#endif

    SERVER_HOST = host;
    SERVER_PORT = host_port;
    SERVER_PROXY_MODE = proxy_mode;

	base = event_base_new();
	if (!base) {
		fprintf(stderr, "Couldn't create an event_base: exiting\n");
		return 1;
	}

	/* Create a new evhttp object to handle requests. */
	http = evhttp_new(base);
	if (!http) {
		fprintf(stderr, "couldn't create evhttp. Exiting.\n");
		return 1;
	}

	/* The /dump URI will dump all requests to stdout and say 200 ok. */
	evhttp_set_cb(http, "/dump", dump_request_cb, NULL);

	/* We want to accept arbitrary requests, so we need to set a "generic"
	 * cb.  We can also add callbacks for specific paths. */
	evhttp_set_gencb(http, send_document_cb, NULL);

	/* Now we tell the evhttp what port to listen on */
	handle = evhttp_bind_socket_with_handle(http, "0.0.0.0", port);
	if (!handle) {
		fprintf(stderr, "couldn't bind to port %d. Exiting.\n",
		    (int)port);
		return 1;
	}

	{
		/* Extract and display the address we're listening on. */
		struct sockaddr_storage ss;
		evutil_socket_t fd;
		ev_socklen_t socklen = sizeof(ss);
		char addrbuf[128];
		void *inaddr;
		const char *addr;
		int got_port = -1;
		fd = evhttp_bound_socket_get_fd(handle);
		memset(&ss, 0, sizeof(ss));
		if (getsockname(fd, (struct sockaddr *)&ss, &socklen)) {
			perror("getsockname() failed");
			return 1;
		}
		if (ss.ss_family == AF_INET) {
			got_port = ntohs(((struct sockaddr_in*)&ss)->sin_port);
			inaddr = &((struct sockaddr_in*)&ss)->sin_addr;
		} else if (ss.ss_family == AF_INET6) {
			got_port = ntohs(((struct sockaddr_in6*)&ss)->sin6_port);
			inaddr = &((struct sockaddr_in6*)&ss)->sin6_addr;
		} else {
			fprintf(stderr, "Weird address family %d\n",
			    ss.ss_family);
			return 1;
		}
		addr = evutil_inet_ntop(ss.ss_family, inaddr, addrbuf,
		    sizeof(addrbuf));
		if (addr) {
			printf("Listening on %s:%d\n", addr, got_port);
			evutil_snprintf(uri_root, sizeof(uri_root),
			    "http://%s:%d",addr,got_port);
		} else {
			fprintf(stderr, "evutil_inet_ntop failed\n");
			return 1;
		}
	}

	(void) event_base_dispatch(base);
    printf ("event loop exit\n");

    event_base_free(base);

	return 0;
}

